﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using LukasKopenec.LocalizationSupport;
using Notoric.Commanding;
using Notoric.Commanding.CommandBase;
using Notoric.Model.Elements;
using Notoric.Model.Validation;
using Notoric.Model.Packaging;

namespace Notoric.ViewModel
{
    /// <summary>
    /// Implements the core functionality for view-model elements.
    /// </summary>
    /// <remarks>
    /// <para>
    /// A view-model element adapts a corresponding <see cref="ModelElement"/> to a Windows Presentation Foundation based view.
    /// The adaptation consists of three principal parts:
    /// <list type="bullet">
    /// <item>
    ///     Conversion between model and view units (e.g. sizes in model are expressed in centimeters 
    ///     whilst in the view device-independent pixels are used).
    /// </item>
    /// <item>
    ///     Use of the model validation interface and its adaptation to the <see cref="IDataErrorInfo"/> interface
    ///     used in the Windows Presentation Foundation as a standard way to inform the user about data validation problems.
    /// </item>
    /// <item>
    ///     Creation of commands that manipulates the adapted model element.
    /// </item>
    /// </list>
    /// </para>
    /// <para>
    /// When the view sets a property value on the view-model element, it is not immediately propagated to the model.
    /// The changes are kept in an <see cref="UnsavedChanges"/> dictionary that contains the names of the modified
    /// properties and maps them to the commands that changes the corresponding properties in the model
    /// until the <see cref="CommitChanges()"/> or <see cref="AbortChanges"/> method is called.
    /// </para>
    /// <para>
    /// In this time the view can check the validity of the data it has set through the <see cref="IDataErrorInfo"/> interface.
    /// If some validation rules are still broken when the <see cref="CommitChanges()"/> is called an <see cref="InvalidOperationException"/>
    /// is thrown and no change is propagated to the model.
    /// </para>
    /// <para>
    /// The view should never directly access the adapted model element. The <see cref="AdaptedElement"/> property was made
    /// public inorder to allow third-party commands to access the element if necessary.
    /// </para>
    /// </remarks>
	public abstract class ViewModelElement : IDataErrorInfo, INotifyPropertyChanged, IDisposable
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="ViewModelElement"/> instance.
        /// </summary>
        /// <param name="adaptedElement">
        /// <para>
        /// Type: <see cref="ModelElement"/>
        /// </para>
        /// <para>
        /// The model element instance adapted by this view-model element instance.
        /// This must not be <see langword="null"/>.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="ViewModelElement"/>
        /// </para>
        /// <para>
        /// The view-model element that directly owns this element.
        /// This may be <see langword="null"/> if this element is the root of an elements hierarchy.
        /// </para>
        /// </param>
        /// <param name="undoStack">
        /// <para>
        /// Type: <see cref="CommandStack"/>
        /// </para>
        /// <para>
        /// The undo stack for this element instance.
        /// </para>
        /// </param>
        /// <param name="redoStack">
        /// <para>
        /// Type: <see cref="CommandStack"/>
        /// </para>
        /// <para>
        /// The redo stack for this element instance.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="adaptedElement"/>, <paramref name="undoStack"/> or <paramref name="redoStack"/> is <see langword="null"/>.
        /// </exception>
        public ViewModelElement(ModelElement adaptedElement, ViewModelElement owner, CommandStack undoStack, CommandStack redoStack)
        {
            if (adaptedElement == null)
                throw new ArgumentNullException("adaptedElement");
            if (undoStack == null)
                throw new ArgumentNullException("undoStack");
            if (redoStack == null)
                throw new ArgumentNullException("redoStack");

            this.adaptedElement = adaptedElement;
            this.owner = owner;
            this.undoStack = undoStack;
            this.redoStack = redoStack;

            monitoredOwners = new List<ViewModelElement>();
            unsavedChanges = new Dictionary<string, UndoableCommandBase>();
            adaptedElement.PropertyChanged += new PropertyChangedEventHandler(adaptedElement_PropertyChanged);
            OwnerChanged += new EventHandler<OwnerChangedEventArgs>(ViewModelElement_OwnerChanged);
        }

        /// <summary>
        /// Creates a new <see cref="ViewModelElement"/> instance.
        /// </summary>
        /// <remarks>
        /// <para>
        /// The purpose of this constructor is to enable a delayed model data loading.
        /// The model implementation does not load the data of an <see cref="XmlPackagePart"/> until
        /// its <see cref="XmlPackagePart.RootElement"/> property is queried for the first time.
        /// </para>
        /// <para>
        /// The other constructor requires a direct reference to the adapted model element, which would cause
        /// an immediate data loading for the root model elements. This one stores the package part instead
        /// and does not read the root element reference until it is queried for the first time.
        /// </para>
        /// </remarks>
        /// <param name="adaptedPart">
        /// <para>
        /// Type: <see cref="XmlPackagePart"/>
        /// </para>
        /// <para>
        /// The model package part instance adapted by this view-model element instance.
        /// This must not be <see langword="null"/>.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="ViewModelElement"/>
        /// </para>
        /// <para>
        /// The view-model element that directly owns this element.
        /// This may be <see langword="null"/> if this element is the root of an elements hierarchy.
        /// </para>
        /// </param>
        /// <param name="undoStack">
        /// <para>
        /// Type: <see cref="CommandStack"/>
        /// </para>
        /// <para>
        /// The undo stack for this element instance.
        /// </para>
        /// </param>
        /// <param name="redoStack">
        /// <para>
        /// Type: <see cref="CommandStack"/>
        /// </para>
        /// <para>
        /// The redo stack for this element instance.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="adaptedPart"/>, <paramref name="undoStack"/> or <paramref name="redoStack"/> is <see langword="null"/>.
        /// </exception>
        public ViewModelElement(XmlPackagePart adaptedPart, ViewModelElement owner, CommandStack undoStack, CommandStack redoStack)
        {
            if (adaptedPart == null)
                throw new ArgumentNullException("adaptedElement");
            if (undoStack == null)
                throw new ArgumentNullException("undoStack");
            if (redoStack == null)
                throw new ArgumentNullException("redoStack");

            this.adaptedPart = adaptedPart;
            this.owner = owner;
            this.undoStack = undoStack;
            this.redoStack = redoStack;

            monitoredOwners = new List<ViewModelElement>();
            unsavedChanges = new Dictionary<string, UndoableCommandBase>();
            OwnerChanged += new EventHandler<OwnerChangedEventArgs>(ViewModelElement_OwnerChanged);
        }

        #endregion

        #region IDataErrorInfo Members

        /// <inheritdoc/>
        public string Error
        {
            get { return null;  }
        }

        /// <summary>
        /// Gets the error message for the property with the given name.
        /// </summary>
        /// <param name="columnName">The name of the property whose error message to get.</param>
        /// <remarks>
        /// This method validates the property using the property with the same name on the <see cref="ModelElement"/>.
        /// If this is not desirable (the <paramref name="columnName"/> property adapts a different property or multiple properties
        /// of the <see cref="ModelElement"/> the class should override this method.
        /// </remarks>
        /// <returns>The error message for the property or <see langword="null"/> if the property has no error</returns>
        public string this[string columnName]
        {
            get 
            {
                // Validates only unsaved values, the values that are in the model are supposed to be valid
                if (unsavedChanges.ContainsKey(columnName))
                {
                    // Get the structure of the requested property
                    var propInfo = GetType().GetProperty(columnName);
                    if (propInfo == null)
                        return String.Format("The property {0} does not exist on {1}!", columnName, GetType().Name);

                    // Get the getter for the requested property
                    var propGetInfo = propInfo.GetGetMethod();
                    if (propGetInfo == null)
                        return String.Format("The property {0}.{1} is not readable!", GetType().Name, columnName);

                    // Get the value of the requested property
                    var value = propGetInfo.Invoke(this, null);

                    // Request the model element to validate the value
                    Rule[] brokenRules = null;
                    if (!AdaptedElement.ValidatePropertyValue(columnName, value, out brokenRules))
                    {
                        // The value is not valid, join the descriptions of all the broken validation rules.
                        var reportBuilder = new StringBuilder();
                        foreach (Rule rule in brokenRules)
                        {
                            // Try to get the localized description from resources, if not available, take the english description.
                            string description = (string)LocalizationManager.GetValue(rule.DescriptionKey);
                            if (description == null)
                                description = rule.Description;

                            reportBuilder.AppendLine(description);
                        }

                        return reportBuilder.ToString();
                    }
                }

                return null;
            }
        }

        #endregion

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Occurs when the value of some property has changed.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region IDisposable Members

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <remarks>
        /// <para>
        /// Use this method to close or release unmanaged resources such as files, streams, and handles held by an instance 
        /// of the class that implements this interface. By convention, this method is used for all tasks associated 
        /// with freeing resources held by an object, or preparing an object for reuse.
        /// </para>
        /// <para>
        /// When implementing this method, ensure that all held resources are freed by propagating the call through 
        /// the containment hierarchy. For example, if an object A allocates an object B, and object B allocates an object C, 
        /// then A's <see cref="Dispose"/> implementation must call <see cref="Dispose"/> on B, which must in turn call <see cref="Dispose"/> on C. 
        /// An object must also call the <see cref="Dispose"/> method of its base class if the base class implements <see cref="IDisposable"/>
        /// </para>
        /// <para>
        /// The <see cref="ViewModelElement"/> unbinds the <see cref="Model.Elements.ModelElement.PropertyChanged"/> listener from the <see cref="AdaptedElement"/>.
        /// </para>
        /// </remarks>
        public virtual void Dispose()
        {
            adaptedElement.PropertyChanged -= adaptedElement_PropertyChanged;
        }

        #endregion

        #region Events

        /// <summary>
        /// Occurs when the owner of this element has changed.
        /// </summary>
        public event EventHandler<OwnerChangedEventArgs> OwnerChanged;

        #endregion

        #region Event Handlers

        /// <summary>
        /// Occurs when the value of some property of the <see cref="adaptedElement"/> has changed.
        /// </summary>
        /// <remarks>
        /// <b>Operation:</b><br/>
        /// The handler calls <see cref="AdaptedElement_PropertyChanged"/>
        /// and if it returns <see langword="true"/> it resends the event via its own <see cref="PropertyChanged"/> event.
        /// </remarks>
        /// <param name="sender">The object that has sent the event</param>
        /// <param name="e">Event data</param>
        private void adaptedElement_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (AdaptedElement_PropertyChanged(e))
                RaisePropertyChanged(e);
        }

        /// <summary>
        /// Occurs when the direct or indirect owner of this element has changed.
        /// </summary>
        /// <remarks>
        /// <b>Operation:</b><br/>
        /// <para>
        /// The handler rebinds all the event listeners bound to elements that are above
        /// the changed owner and re-raises this event via its own <see cref="OwnerChanged"/> event.
        /// </para>
        /// <para>
        /// <i>Note:</i><br/>
        /// In reality the handler rebinds all the event listeners, because the implementation
        /// is much easier. But semantically it corresponds to what is written in the paragraph above.
        /// </para>
        /// </remarks>
        /// <param name="sender">Object that has sent the event</param>
        /// <param name="e">Event data</param>
        private void ViewModelElement_OwnerChanged(object sender, OwnerChangedEventArgs e)
        {
            for (int i = 0; i < monitoredOwners.Count; ++i)
            {
                var owner = monitoredOwners[i];

                owner.PropertyChanged -= owner_PropertyChanged;

                var el = this.owner;
                while (el != null && !(el.GetType() == owner.GetType()))
                    el = el.Owner;

                if (el != null)
                {
                    el.PropertyChanged += new PropertyChangedEventHandler(owner_PropertyChanged);
                    monitoredOwners[i] = el;
                }
            }

            if (OwnerChanged != null)
                OwnerChanged(this, e);
        }

        /// <summary>
        /// Occurs when the value of some property of a monitored owner has changed.
        /// </summary>
        /// <remarks>
        /// <b>Operation:</b><br/>
        /// The handler invokes the <see cref="Owner_PropertyChanged"/> virtual method and passes it the owner's instance
        /// and the event data.
        /// </remarks>
        /// <param name="sender">Object that has sent the event</param>
        /// <param name="e">Event data</param>
        private void owner_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            Owner_PropertyChanged((ViewModelElement)sender, e);
        }

        #endregion

        #region Protected Methods

        /// <summary>
        /// This method is called when some property of the <see cref="AdaptedElement"/> has changed.
        /// </summary>
        /// <remarks>
        /// Derived classes can override this method in order to react to the changes in adapted element
        /// or to prevent this element to resend the event.
        /// </remarks>
        /// <param name="e">
        /// <para>
        /// Type: <see cref="PropertyChangedEventArgs"/>
        /// </para>
        /// <para>
        /// The data of the property changed event.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="Boolean"/>
        /// </para>
        /// <para>
        /// Indicates whether the event should be resent by the <see cref="PropertyChanged"/> event
        /// of this element (return value <see langword="true"/>) or not (<see langword="false"/>).
        /// </para>
        /// </returns>
        protected virtual bool AdaptedElement_PropertyChanged(PropertyChangedEventArgs e)
        {
            return true;
        }

        /// <summary>
        /// Helper method for getting the actual value of a given property.
        /// </summary>
        /// <remarks>
        /// <para>
        /// This method just encapsulates the retrieval of the actual property value
        /// that can be stored either in the <see cref="UnsavedChanges"/> collection
        /// if it has been changed and not yet committed or in the <see cref="AdaptedElement"/>.
        /// </para>
        /// <para>
        /// <b>Caution!</b><br/>
        /// This method can only be used for properties that have the same name in the <see cref="AdaptedElement"/>
        /// as in the <see cref="UnsavedChanges"/> collection and that use the <see cref="SetterCommand{T}"/>
        /// for committing the changes to the model!
        /// </para>
        /// <para>
        /// This method is not ment as a universal getter, it was written just to ease the writing of most
        /// of the view-model property getters.
        /// </para>
        /// </remarks>
        /// <typeparam name="T">The type of the property value</typeparam>
        /// <param name="propertyName">
        /// <para>
        /// Type: <see cref="String"/>
        /// </para>
        /// <para>
        /// The name of the property.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <typeparamref name="T"/>
        /// </para>
        /// <para>
        /// The property value.
        /// </para>
        /// </returns>
        /// <exception cref="ArgumentException">
        /// The <see cref="AdaptedElement"/> does not have a property called <paramref name="propertyName"/>.
        /// </exception>
        /// <exception cref="InvalidCastException">
        /// The property is in the <see cref="UnsavedChanges"/> map, but the command cannot be cast to <see cref="SetterCommand{T}"/>.<br/>
        /// -or-<br/>
        /// The property exists on the <see cref="AdaptedElement"/>, but its value cannot be cast to <typeparamref name="T"/>.
        /// </exception>
        protected T GetPropertyValue<T>(string propertyName)
        {
            if (UnsavedChanges.ContainsKey(propertyName))
            {
                var cmd = (SetterCommand<T>)UnsavedChanges[propertyName];
                return cmd.Value;
            }
            else
                return (T)AdaptedElement.GetPropertyValue(propertyName);
        }

        /// <summary>
        /// This method is called when some property of some of the monitored owners has changed.
        /// </summary>
        /// <remarks>
        /// This method is called in reaction to <see cref="ViewModelElement.PropertyChanged"/> event
        /// that was raised by an element that directly or indirectly owns this element and was previously
        /// registered to monitoring by a call to <see cref="RegisterToOwnerPropertyChanged{T}"/> method.
        /// The derived classes can override it to react to this event.
        /// </remarks>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="ViewModelElement"/>
        /// </para>
        /// <para>
        /// The element (owner) that has raised the event.
        /// </para>
        /// </param>
        /// <param name="e">
        /// <para>
        /// Type: <see cref="PropertyChangedEventArgs"/>
        /// </para>
        /// <para>
        /// The data of the property changed event.
        /// </para>
        /// </param>
        protected virtual void Owner_PropertyChanged(ViewModelElement owner, PropertyChangedEventArgs e)
        {
        }

        /// <summary>
        /// Raises the <see cref="PropertyChanged"/> event on this <see cref="ViewModelElement"/> instance.
        /// </summary>
        /// <param name="propertyName">
        /// <para>
        /// Type: <see cref="System.String"/>
        /// </para>
        /// <para>
        /// The name of the property whose value has changed.
        /// </para>
        /// </param>
        protected void RaisePropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        /// <summary>
        /// Raises the <see cref="PropertyChanged"/> event on this <see cref="ViewModelElement"/> instance.
        /// </summary>
        /// <param name="e">
        /// <para>
        /// Type: <see cref="PropertyChangedEventArgs"/>
        /// </para>
        /// <para>
        /// Event data.
        /// </para>
        /// </param>
        protected void RaisePropertyChanged(PropertyChangedEventArgs e)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, e);
        }

        #endregion

        #region Protected Properties

        /// <summary>
        /// Gets the value converter that converts between the brushes used in the model and in the view.
        /// </summary>
        protected static readonly Converters.BrushConverter BrushConverter = new Converters.BrushConverter();

        /// <summary>
        /// Gets the value converter that converts between the length units used in the model and in the view.
        /// </summary>
        protected static readonly Converters.LengthConverter LengthConverter = new Converters.LengthConverter();

        /// <summary>
        /// Gets the value converter that converts between the structures used in the model an in the view.
        /// </summary>
        protected static readonly Converters.StructuresConverter StructuresConverter = new Converters.StructuresConverter();

        /// <summary>
        /// Gets the collection of the changes that were made since the last <see cref="CommitChanges()"/> / <see cref="AbortChanges"/>.
        /// </summary>
        protected Dictionary<string, UndoableCommandBase> UnsavedChanges
        {
            get { return unsavedChanges; }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Aborts the changes made to this element since the last <see cref="CommitChanges()"/>
        /// </summary>
        /// <remarks>
        /// This method raises the <see cref="PropertyChanged"/> event for each property in <see cref="UnsavedChanges"/>.
        /// </remarks>
        public void AbortChanges()
        {
            lock (unsavedChanges)
            {
                string[] properties = new string[unsavedChanges.Keys.Count];
                unsavedChanges.Keys.CopyTo(properties, 0);

                unsavedChanges.Clear();

                foreach (string property in properties)
                    RaisePropertyChanged(property);
            }
        }

        /// <summary>
        /// Commits the changes made to this element and reflects them in the model.
        /// </summary>
        /// <exception cref="InvalidOperationException">
        /// The element contains invalid values that are not allowed in the model.
        /// </exception>
        public void CommitChanges()
        {
            UndoableCommandBase commitCommand;

            CommitChanges(out commitCommand);
            if (commitCommand != null)
                commitCommand.Execute(null);
        }

        /// <summary>
        /// Creates the command that commits the unsaved changes but does not execute it.
        /// Instead it is returned in the <paramref name="commitCommand"/> parameter.
        /// </summary>
        /// <param name="commitCommand">
        /// <para>
        /// Type: <see cref="UndoableCommandBase"/>
        /// </para>
        /// <para>
        /// Command that commits the unsaved changes in this element.
        /// If the element does not have unsaved changes, this parameter is <see langword="null"/>.
        /// </para>
        /// </param>
        public void CommitChanges(out UndoableCommandBase commitCommand)
        {
            lock (unsavedChanges)
            {
                if (unsavedChanges.Count > 0)
                {
                    // Validate each unsaved change, if any validation fails throw exception
                    foreach (string property in unsavedChanges.Keys)
                    {
                        if (this[property] != null)
                            throw new InvalidOperationException("Some of the changes are not valid and cannot be committed!");
                    }

                    // If there is just one change to commit, execute its command
                    if (unsavedChanges.Count == 1)
                    {
                        var enumerator = unsavedChanges.Values.GetEnumerator();
                        enumerator.MoveNext();

                        var cmd = enumerator.Current;
                        commitCommand = cmd;
                    }
                    else      // Otherwise create a macro command
                    {
                        var macro = new UndoableMacroCommand(UndoStack, RedoStack);
                        macro.Name = String.Format(
                                (string)LocalizationManager.GetValue("Commands.Names.CommitViewModelChanges"),
                                (string)LocalizationManager.GetValue(ElementNameKey)
                            );

                        foreach (UndoableCommandBase cmd in unsavedChanges.Values)
                            macro.Commands.Add(cmd);

                        commitCommand = macro;
                    }

                    // Clear the unsaved changes map
                    unsavedChanges.Clear();
                }
                else
                    commitCommand = null;
            }
        }

        /// <summary>
        /// Finds a view-model element that owns (directly or indirectly) this element and has a given type.
        /// </summary>
        /// <typeparam name="T">The type of the searched owner (must be a subtype of <see cref="ViewModelElement"/></typeparam>
        /// <returns>
        /// <para>
        /// Type: <typeparamref name="T"/>
        /// </para>
        /// <para>
        /// A reference to the searched owner or <see langword="null"/> if no such owner exists.
        /// </para>
        /// </returns>
        public T FindOwner<T>() where T : ViewModelElement
        {
            var el = owner;
            while (el != null && !(el is T))
                el = el.owner;

            return (T)el;
        }

        /// <summary>
        /// Migrates this element and its content to a new owner.
        /// </summary>
        /// <remarks>
        /// <para>
        /// This method sets the <see cref="Owner"/> property to <paramref name="newOwner"/> and
        /// raises the <see cref="OwnerChanged"/> event.
        /// </para>
        /// <para>
        /// However, this method does not remove this element from the old owner, nor does it paste it to the new one.
        /// This is the responsibility of the caller.
        /// </para>
        /// </remarks>
        /// <param name="newOwner">
        /// <para>
        /// Type: <see cref="ViewModelElement"/>
        /// </para>
        /// <para>
        /// The new owner of this element.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="newOwner"/> is <see langword="null"/>.
        /// </exception>
        public void MigrateTo(ViewModelElement newOwner)
        {
            if (newOwner == null)
                throw new ArgumentNullException("newOwner");

            AdaptedElement.MigrateTo(newOwner.AdaptedElement);

            var oldOwner = this.owner;
            this.owner = newOwner;

            if (OwnerChanged != null)
                OwnerChanged(this, new OwnerChangedEventArgs(oldOwner, newOwner));
            RaisePropertyChanged("Owner");
        }

        /// <summary>
        /// Registers an event listener to the <see cref="ViewModelElement.PropertyChanged"/> event
        /// of a direct or indirect owner of this element that has a given type.
        /// </summary>
        /// <remarks>
        /// <para>
        /// This method ensures the correct listener rebinding in a case when this element is migrated
        /// to a different owner.
        /// </para>
        /// <para>
        /// The derived class can override the <see cref="Owner_PropertyChanged"/> method to react
        /// to the <see cref="ViewModelElement.PropertyChanged"/> event of the registered owner.
        /// </para>
        /// </remarks>
        /// <typeparam name="T">The type of the owner to which the element wants to bind.</typeparam>
        public void RegisterToOwnerPropertyChanged<T>() where T : ViewModelElement
        {
            var owner = FindOwner<T>();

            if (owner != null && !monitoredOwners.Contains(owner))
            {
                owner.PropertyChanged += new PropertyChangedEventHandler(owner_PropertyChanged);
                monitoredOwners.Add(owner);
            }
        }

        /// <summary>
        /// Unregisters an event listener from the <see cref="ViewModelElement.PropertyChanged"/> event
        /// of a direct or indirect owner of this element that has a given type.
        /// </summary>
        /// <typeparam name="T">The type of the owner from which the element wants to unbind.</typeparam>
        /// <seealso cref="RegisterToOwnerPropertyChanged"/>
        public void UnregisterFromOwnerPropertyChanged<T>() where T : ViewModelElement
        {
            var owner = monitoredOwners.Find(o => o is T);
            if (owner != null)
            {
                owner.PropertyChanged -= owner_PropertyChanged;
                monitoredOwners.Remove(owner);
            }
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets a reference to the model element that is being
        /// adapted by this view-model element.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="ModelElement"/>
        /// </para>
        /// <para>
        /// The model element that is being adapted by this view-model element.
        /// </para>
        /// </value>
        public ModelElement AdaptedElement
        {
            get 
            {
                if (adaptedElement == null)
                {
                    adaptedElement = adaptedPart.RootElement;
                    adaptedElement.PropertyChanged += new PropertyChangedEventHandler(adaptedElement_PropertyChanged);
                }
                
                return adaptedElement; 
            }
        }

        /// <summary>
        /// Gets a reference to the package part adapted by this element.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="XmlPackagePart"/>
        /// </para>
        /// <para>
        /// The package part adapted by this element.
        /// This value can be <see langword="null"/> if the element does not adapt a part.
        /// </para>
        /// </value>
        public XmlPackagePart AdaptedPart
        {
            get { return adaptedPart; }
        }

        /// <summary>
        /// Gets the dictionary key that identifies a user-friendly name of this view-model element.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="System.String"/>
        /// </para>
        /// <para>
        /// This value is used when there is a necessity to inform the user about the (logical)
        /// element he was interacting with. For example it is used by <see cref="CommitChanges()"/>
        /// for setting the name of the macro command.
        /// </para>
        /// </value>
        public abstract string ElementNameKey
        {
            get;
        }

        /// <summary>
        /// Gets a reference to the view-model element that directly owns this element.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="ViewModelElement"/>
        /// </para>
        /// <para>
        /// A view-model element that directly owns this element. This value can be <see langword="null"/>
        /// if the element has no owner.
        /// </para>
        /// </value>
        public ViewModelElement Owner
        {
            get { return owner; }
        }

        /// <summary>
        /// Gets or sets a reference to the redo command stack for this element.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="CommandStack"/>
        /// </para>
        /// <para>
        /// The command stack where the commands issued by this view-model element
        /// instance are going to be put for a possible redo when they have been undone.
        /// </para>
        /// </value>
        /// <exception cref="ArgumentNullException">
        /// The value is <see langword="null"/>.
        /// </exception>
        public CommandStack RedoStack
        {
            get { return redoStack; }
            set
            {
                if (value == null)
                    throw new ArgumentNullException("value");

                redoStack = value;
                RaisePropertyChanged("RedoStack");
            }
        }

        /// <summary>
        /// Gets or sets a reference to the undo command stack for this element.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="CommandStack"/>
        /// </para>
        /// <para>
        /// The command stack where the commands issued by this view-model element
        /// instance will be put after their execution.
        /// </para>
        /// </value>
        /// <exception cref="ArgumentNullException">
        /// The value is <see langword="null"/>.
        /// </exception>
        public CommandStack UndoStack
        {
            get { return undoStack; }
            set
            {
                if (value == null)
                    throw new ArgumentNullException("value");

                undoStack = value;
                RaisePropertyChanged("UndoStack");
            }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds a reference to the model element adapted by this class.
        /// </summary>
        private ModelElement adaptedElement;
        /// <summary>
        /// Holds a reference to the adapted model package part.
        /// </summary>
        private XmlPackagePart adaptedPart;
        /// <summary>
        /// Holds the list of owners of this element whose <see cref="PropertyChanged"/> event is monitored.
        /// </summary>
        private List<ViewModelElement> monitoredOwners;
        /// <summary>
        /// Holds a reference to the view-model element that owns this element.
        /// </summary>
        private ViewModelElement owner;
        /// <summary>
        /// Holds a reference to the redo stack for this element.
        /// </summary>
        private CommandStack redoStack;
        /// <summary>
        /// Holds a reference to the undo stack for this element.
        /// </summary>
        private CommandStack undoStack;
        /// <summary>
        /// Holds a dictionary of unsaved changes (maps Property Name to Command that will perform the change).
        /// </summary>
        private Dictionary<string, UndoableCommandBase> unsavedChanges;

        #endregion
    }

    /// <summary>
    /// Holds the data of the <see cref="ViewModelElement.OwnerChanged"/> event.
    /// </summary>
    public class OwnerChangedEventArgs : EventArgs
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="OwnerChangedEventArgs"/> instance.
        /// </summary>
        /// <param name="oldOwner">
        /// <para>
        /// Type: <see cref="ViewModelElement"/>
        /// </para>
        /// The old owner of the element.
        /// </param>
        /// <param name="newOwner">
        /// <para>
        /// Type: <see cref="ViewModelElement"/>
        /// </para>
        /// <para>
        /// The new owner of the element.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Any of the arguments is <see langword="null"/>.
        /// </exception>
        public OwnerChangedEventArgs(ViewModelElement oldOwner, ViewModelElement newOwner)
        {
            if (oldOwner == null)
                throw new ArgumentNullException("oldOwner");
            if (newOwner == null)
                throw new ArgumentNullException("newOwner");

            OldOwner = oldOwner;
            NewOwner = newOwner;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets the old owner of the element.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="ViewModelElement"/>
        /// </para>
        /// <para>
        /// The old owner of the element.
        /// </para>
        /// </value>
        public ViewModelElement OldOwner
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the new owner of the element.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="ViewModelElement"/>
        /// </para>
        /// <para>
        /// The new owner of the element.
        /// </para>
        /// </value>
        public ViewModelElement NewOwner
        {
            get;
            private set;
        }

        #endregion
    }
}
